package com.mysteel.product.web;

import com.mysteel.product.entity.CategoryEntity;
import com.mysteel.product.service.CategoryService;
import com.mysteel.product.vo.web.Catalog2Vo;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * ProjectName: mail
 * ClassName: IndexController
 * Description:
 * date: 2021/01/23/023 15:33
 *
 * @author xiaochao
 */
@Controller
public class IndexController {

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping({"/","index.html"})
    public String index(Model model){
        // 查询一级分类
        List<CategoryEntity> list = categoryService.getLevel1Category();
        model.addAttribute("categories",list);
        return "index";
    }

    @ResponseBody
    @GetMapping("/index/json/catalog.json")
    public Map<String, List<Catalog2Vo>> getCatalogJson(Model model){
        Map<String, List<Catalog2Vo>> catalogMap = categoryService.getCatalogJson();
        return catalogMap;
    }

    @ResponseBody
    @GetMapping("/hello")
    public String hello() throws InterruptedException {
        // 获取分布式锁
        RLock lock = redissonClient.getLock("anyLock");

        /**
         * lock.lock();
         * 锁的自动续期，如果业务处理时间超长，锁自动过期（默认30s）
         * 加锁的业务完成，就不会给当前锁自动续期，不用担心死锁问题
         * 1、如果未指定时间，使用看门狗默认时间（30s），只要占锁成功，就会启动定时任务(看门狗时间 / 3 运行一次，默认10s执行一次)，给锁重新设置过期时间（30s）
         *
         * lock.lock(10,TimeUnit.SECONDS);  推荐使用
         * 阻塞等待(10s锁自动失效，不会自动续期，线程解锁会失败，自动解锁时间一定要大于业务执行时间)
         * 2、如果传递了锁的超时时间，就发送脚本给redis执行，超时时间就是设置的超时时间
         * 无需调用unlock方法手动解锁
         *
         */
        lock.lock(10,TimeUnit.SECONDS);
        try {
            System.out.println(Thread.currentThread().getName() + "获取锁成功，执行业务代码...");
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "释放锁成功...");
            lock.unlock();
        }

        return "hello";
    }

    /**
     * 保证一定能读到最新的数据，数据修改期间，写锁是排它锁
     * 模拟业务写过程
     * 1、写 + 读：等待写锁释放
     * 2、写 + 写：阻塞进行
     * 3、读 + 写：读锁释放后写锁才能加锁
     * 4、读 + 读：相当于无锁，会同时加锁成功
     * 总结：只要有写锁，就需要等待
     * @return
     */
    @ResponseBody
    @GetMapping("/write")
    public String writeValue(){
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        String s = "";
        RLock writeLock = readWriteLock.writeLock();
        try {
            // 改数据加写锁
            writeLock.lock();
            System.out.println("写锁加锁成功..." + Thread.currentThread().getId());
            s = UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("writeValue",s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            writeLock.unlock();
            System.out.println("写锁释放成功..." + Thread.currentThread().getId());
        }
        return s;
    }

    /**
     * 如果写锁存在（有线程在写数据），读锁就要等待写锁释放，若写锁不存在，读不受影响（共享锁）
     * 模拟业务读取过程
     * @return
     */
    @ResponseBody
    @GetMapping("/read")
    public String readValue(){
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        RLock readLock = readWriteLock.readLock();
        String value = "";
        // 加读锁
        readLock.lock();
        System.out.println("读锁加锁成功..." + Thread.currentThread().getId());
        try {
            value = redisTemplate.opsForValue().get("writeValue");
            Thread.sleep(30000);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readLock.unlock();
            System.out.println("读锁释放成功..." + Thread.currentThread().getId());
        }
        return value;
    }

    /**
     * 闭锁：CountDownLatch，等待其他线程执行完成才释放锁
     * 模拟：放假锁门，所有班级的人都走了才锁门
     */
    @ResponseBody
    @GetMapping("/lockDoor")
    public String lockDoor(){
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("close-door");
        try {
            //等待闭锁完成
            countDownLatch.trySetCount(5);
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "闭校...";
    }

    @ResponseBody
    @GetMapping("/finish/{id}")
    public String finish(@PathVariable("id") Long id){
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("close-door");
        countDownLatch.countDown();
        return id + "班的人都走了...";
    }

    /**
     * 信号量：Semaphore
     * 模拟停车场停车
     */
    @ResponseBody
    @GetMapping("/park")
    public String park(){
        RSemaphore park = redissonClient.getSemaphore("park");

        try {
            // 获取一个信号量（占用一个车位）
            // tryAcquire：尝试获取，如果没有就返回false（可做限流）;acquire：未获取到就一直等待，直至获取到信号量
            park.acquire();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "ok";
    }

    @ResponseBody
    @GetMapping("/remove")
    public String remove(){
        RSemaphore park = redissonClient.getSemaphore("park");
        // 释放一个信号量（放开一个车位）
        park.release();
        return "bye";
    }

}
